/********************************************************************************
 * Copyright (c) {date} Red Hat Inc. and/or its affiliates and others
 *
 * This program and the accompanying materials are made available under the 
 * terms of the Apache License, Version 2.0 which is available at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0 
 ********************************************************************************/
import org.eclipse.ceylon.compiler.typechecker.parser.CeylonLexer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;

/**
 * Generates TokenTypes.java using tokens recognized by CeylonLexer.java.
 */
public class TokenTypesGenerator {

    private static void writeClassFromTokenList(File outputFile, String prefix, FieldSerializer fieldSerializer, String suffix) throws IOException, IllegalAccessException {
        try (FileOutputStream os = new FileOutputStream(outputFile)) {
            os.write(prefix.getBytes());

            for (Field field : CeylonLexer.class.getFields()) {
                if (field.getDeclaringClass() == CeylonLexer.class) {
                    fieldSerializer.writeLineForField(os, field);
                }
            }

            os.write(suffix.getBytes());
        }
    }

    public static void main(String[] args) throws IOException, IllegalAccessException {
        writeTokenTypes(args[0]);
        writeCeylonTokens(args[0]);
    }

    private static void writeTokenTypes(String outputDirectory) throws IOException, IllegalAccessException {
        writeClassFromTokenList(new File(outputDirectory, "org/eclipse/ceylon/ide/intellij/psi/TokenTypes.java"),
                "// Automatically generated by TokenTypesGenerator.java, do not edit!\n" +
                        "package org.eclipse.ceylon.ide.intellij.psi;\n\n" +
                        "import com.intellij.psi.tree.IElementType;\n\n" +
                        "import java.lang.reflect.Field;\n" +
                        "import java.util.HashMap;\n" +
                        "import java.util.Map;\n\n" +
                        "public enum TokenTypes {\n\n",
                new FieldSerializer() {
                    @Override
                    public void writeLineForField(OutputStream os, Field field) throws IOException {
                        try {
                            os.write(("    " + field.getName() + "(" + field.get(null) + "),\n").getBytes());
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                },
                "\n    ;\n\n" +
                        "    static final Map<IElementType, TokenTypes> byIElementType = new HashMap<>();\n" +
                        "    static final Map<Integer, TokenTypes> byIndex = new HashMap<>();\n" +
                        "\n" +
                        "    static {\n" +
                        "        final Field[] fields = CeylonTokens.class.getDeclaredFields();\n" +
                        "        final Map<String, IElementType> ceylonTokensFields = new HashMap<>();\n" +
                        "        for (Field tokenField : fields) {\n" +
                        "            try {\n" +
                        "                final IElementType elt = (IElementType) tokenField.get(null);\n" +
                        "                ceylonTokensFields.put(tokenField.getName(), elt);\n" +
                        "            } catch (IllegalAccessException e) {\n" +
                        "                throw new RuntimeException(\"Error accesing CeylonTypes fields.\", e);\n" +
                        "            }\n" +
                        "        }\n" +
                        "        for (TokenTypes tokenType : values()) {\n" +
                        "            tokenType.tokenType = ceylonTokensFields.get(tokenType.name());\n" +
                        "            byIElementType.put(tokenType.tokenType, tokenType);\n" +
                        "            byIndex.put(tokenType.getValue(), tokenType);\n" +
                        "        }\n" +
                        "    }\n" +
                        "\n" +
                        "    private final int value;\n" +
                        "    private IElementType tokenType;\n" +
                        "    \n" +
                        "    private TokenTypes(int value) {\n" +
                        "        this.value = value;\n" +
                        "    }\n" +
                        "\n" +
                        "    public IElementType getTokenType() {\n" +
                        "        return tokenType;\n" +
                        "    }\n" +
                        "    \n" +
                        "    public int getValue() {\n" +
                        "        return value;\n" +
                        "    }\n" +
                        "\n" +
                        "    public static IElementType fromInt(int value) {\n" +
                        "        final TokenTypes tt = byIndex.get(value);\n" +
                        "        assertNotNull(value, tt);\n" +
                        "        return tt.tokenType;\n" +
                        "    }\n" +
                        "\n" +
                        "    public static TokenTypes get(IElementType key) {\n" +
                        "        final TokenTypes tt = byIElementType.get(key);\n" +
                        "        assertNotNull(key, tt);\n" +
                        "        return tt;\n" +
                        "    }\n" +
                        "\n" +
                        "    private static void assertNotNull(Object key, TokenTypes tt) {\n" +
                        "        if (tt == null) {\n" +
                        "            throw new IllegalArgumentException(\"Unknown token type: \" + key);\n" +
                        "        }\n" +
                        "    }\n" +
                        "}"
        );
    }

    private static void writeCeylonTokens(String outputDirectory) throws IOException, IllegalAccessException {
        writeClassFromTokenList(new File(outputDirectory, "org/eclipse/ceylon/ide/intellij/psi/CeylonTokens.java"),
                "// Automatically generated by TokenTypesGenerator.java, do not edit!\n" +
                        "package org.eclipse.ceylon.ide.intellij.psi;\n\n" +

                        "import com.intellij.psi.TokenType;\n" +
                        "import com.intellij.psi.tree.IElementType;\n" +

                        "public interface CeylonTokens {\n",
                new FieldSerializer() {
                    @Override
                    public void writeLineForField(OutputStream os, Field field) throws IOException {
                        if ("WS".equals(field.getName())) {
                            return;
                        }
                        os.write(("    IElementType " + field.getName() + " = new CeylonTokenType(\"" + field.getName() + "\");\n").getBytes());
                    }
                },
                "    IElementType STRING_TEMPLATE = new CeylonTokenType(\"STRING_TEMPLATE\");\n" +
                        "    IElementType STRING_INTERP = new CeylonTokenType(\"STRING_INTERP\");\n" +
                        "    IElementType WS = TokenType.WHITE_SPACE;\n" +
                        "}\n"
        );
    }

    private interface FieldSerializer {
        void writeLineForField(OutputStream os, Field field) throws IOException;
    }
}
